//-*****************************************************************************
//
// Copyright (c) 2013,
//  Sony Pictures Imageworks Inc. and
//  Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// *       Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// *       Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// *       Neither the name of Sony Pictures Imageworks, nor
// Industrial Light & Magic, nor the names of their contributors may be used
// to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//-*****************************************************************************

#include "MriAlembicIn.h"
#include "boost/regex.hpp"

using namespace mariAlembic;
using boost::regex;

#ifdef _WIN32
    ///< Windows DLL export definition for the getPlugins() function
    #define DLLEXPORT __declspec(dllexport)
#else
    ///< A Linux placeholder library export definition to match Windows
    #define DLLEXPORT
#endif

//------------------------------------------------------------------------------
// Function prototypes and data
//------------------------------------------------------------------------------


/// Loads a geometry file
extern "C" MriGeoPluginResult load(MriGeoEntityHandle Entity,
                                   const char *pFileName,
                                   const char **ppMessagesOut);

/// Retrieves the settings for loading a geometry file
extern "C" MriGeoPluginResult getSettings(MriUserItemHandle SettingsHandle,
                                          const char *pFileName);
/// Returns the formats supported by the plug-in
extern "C" MriFileFormatDesc *supportedFormats(int *pNumFormatsOut);


/// Returns the list of plug-ins in this library
extern "C" DLLEXPORT FnPlugin *getPlugins(unsigned int *pNumPlugins);
/// Sets the host information for a plug-in
extern "C" FnPluginStatus setHost(const FnPluginHost *pHost);
/// Returns the suite of functions provided by a plug-in
extern "C" const void *getPluginSuite();
/// Cleans up the plug-in
extern "C" void flushPluginSuite();

///< The host structure, which contains functions that the plug-in can call
MriGeoReaderHostV1 s_Host;

/// The plug-in structure, to provide to the host so it can call functions
/// when needed
MriGeoReaderPluginV1 s_PluginSuite =
{
    &load,
    &getSettings,
    &supportedFormats
};



OptionsData widgetData; // selection qt interface data


int selectionDialog(AlembicReader &data)
{
    // show tree dialog and optionally select items
    MyQDialog Dialog(&data);

    // set ui elements
    Dialog.ui.createTree(data.widgetData->rootTree,
        Dialog.ui.selectionTreeWidget);
    Dialog.ui.createMaterialList(data.widgetData->materialMap,
        Dialog.ui.materialList);


    if (data.widgetData->filterMaterials && data.widgetData->filterSelected)
        Dialog.ui.shapeAndMaterialRadioButton->setChecked(true);
    else if (data.widgetData->filterMaterials)
        Dialog.ui.materialOnlyRadioButton->setChecked(true);
    else if (data.widgetData->filterSelected)
        Dialog.ui.shapeOnlyRadioButton->setChecked(true);
    else
        Dialog.ui.shapeAndMaterialRadioButton->setChecked(true);

    // set selection mode buttons here

    QPushButton *ok = Dialog.ui.buttonBox->button(QDialogButtonBox::Ok);
    ok->setAutoDefault(false);
    ok->setDefault(false);

    QPushButton *cancel = Dialog.ui.buttonBox->button(QDialogButtonBox::Cancel);
    cancel->setAutoDefault(false);
    cancel->setDefault(false);


    // open dialog
    int result = Dialog.exec();
    if (result)
    {

        // get ui settings
        data.widgetData->filterSelectedWithChildren =
            Dialog.ui.allChildrenCheckBox->isChecked();

        // get selection mode button state here
        if (Dialog.ui.shapeAndMaterialRadioButton->isChecked())
        {
            data.widgetData->filterMaterials = true;
            data.widgetData->filterSelected = true;
        }
        else if(Dialog.ui.shapeOnlyRadioButton->isChecked())
        {
            data.widgetData->filterMaterials = false;
            data.widgetData->filterSelected = true;
        }
        else if(Dialog.ui.materialOnlyRadioButton->isChecked())
        {
            data.widgetData->filterMaterials = true;
            data.widgetData->filterSelected = false;
        }


        // get material regex
        QString matRegex = Dialog.ui.materialRegex->text();
        data.widgetData->materialsRegex = matRegex.toAscii().constData();

        // get selection regex
        QString selRegex = Dialog.ui.selectionRegex->text();
        data.widgetData->selectionRegex = selRegex.toAscii().constData();


        // get selected tree items
        if (data.widgetData->filterSelected)
        {
            QList<QTreeWidgetItem *> selected =
                Dialog.ui.selectionTreeWidget->selectedItems();

            for (int i=0; i< selected.size(); i++)
            {
                QString wName = selected[i]->text(1);
                data.widgetData->selectedNames.insert(
                    wName.toAscii().constData());
            }
        }

        // get selected material items
        if (data.widgetData->filterMaterials)
        {
            QList<QListWidgetItem *> selected =
                Dialog.ui.materialList->selectedItems();

            for (int i=0; i< selected.size(); i++)
            {
                QString wName = selected[i]->text();
                data.widgetData->materials.push_back(
                    wName.toAscii().constData());
            }
        }

    }

    return result;
}


void performFiltering(AlembicReader data)
{
    OptionsData &wd = *data.widgetData;

    // filter selected items
    if (wd.filterSelected)
    {
        if (wd.filterSelectedWithChildren)
        {
            // go through the tree and put all selected items and children
            // on the filtered shapes list
            wd.rootTree.markSelected(wd.selectedNames, wd.filteredShapes);
        }
        else
        {
            // just put the selected items on the filtered shapes list
            set<string>::iterator it;
            for(it=wd.selectedNames.begin();
                it!=wd.selectedNames.end(); it++)
            {
                wd.filteredShapes.insert(*it);
            }
        }
    }

    // filter Material via popup
    if (wd.filterMaterials)
    {
        for (unsigned int i=0; i< wd.materials.size(); i++)
            wd.filterMaterial(wd.materials[i]);
    }

    // filter Material via regex
    if (wd.materialsRegex != "")
    {
        try
        {
            map<string, set<string> >::iterator it;
            for (it = wd.materialMap.begin(); it != wd.materialMap.end(); it++)
            {
                string mname = (*it).first;
                regex r(wd.materialsRegex);
                if (boost::regex_match(mname, r))
                    wd.filterMaterial(mname);
            }
        }
        catch ( exception &e )
        {
            cout << "Material regex error." << endl;
        }
    }

    // filter selection via regex
    if (wd.selectionRegex != "")
    {
        try
        {
            for (unsigned int i=0; i<wd.shapeNames.size();i++)
            {
                string sn = data.getShortName(wd.shapeNames[i]);
                regex r(wd.selectionRegex);
                if (regex_match(sn, r))
                    wd.filteredShapes.insert(wd.shapeNames[i]);
            }
        }
        catch ( exception &e )
        {
            cout << "Selection regex error." << endl;
        }
    }

}


MriGeoPluginResult load(MriGeoEntityHandle Entity, const char *pFileName,
                        const char ** /*ppMessagesOut*/)
{
    widgetData.reset(); // reset selection options data
    AlembicReader data(&widgetData, pFileName, &s_Host);

    // check the environment variable to get the name of the material attribute.
    try
    {
        char *matAttrName = getenv("MARI_ALEMBIC_MATERIAL_ATTRIBUTE");
        if (matAttrName != NULL)
            data.materialAttrName = matAttrName;
    }
    catch (const std::exception & e)
    {
        std::cerr << e.what() << std::endl;
    }

    // get the shape names and any attributes we're interested in.
    data.queryAlembicData(widgetData.shapeNames);

    //bring up the qt dialog
    if(!selectionDialog(data))
        return MRI_GPR_FILE_READ_FAILED;

    // filter selected items
    performFiltering(data);

    // load the alembic file
    if(data.loadAlembic(Entity))
    {
        widgetData.reset();
        return MRI_GPR_SUCCEEDED;
    }
    else
    {
        std::cerr << "ERROR: failed when calling data.loadAlembic" << std::endl;
    }

    widgetData.reset();
    return MRI_GPR_FILE_READ_FAILED;
}


//------------------------------------------------------------------------------

MriFileFormatDesc *supportedFormats(int *pNumFormatsOut)
{
    static MriFileFormatDesc s_Formats[1] =
    {
        { "abc", "Alembic abc file" }
    };
    *pNumFormatsOut = 1;
    return s_Formats;
}

//------------------------------------------------------------------------------


MriGeoPluginResult getSettings(MriUserItemHandle /*SettingsHandle*/,
                              const char * /*pFileName*/)
{
    return MRI_GPR_SUCCEEDED;
}

//------------------------------------------------------------------------------
// Plug-in interface function definitions
//------------------------------------------------------------------------------

const void *getPluginSuite()
{
    return &s_PluginSuite;
}

//------------------------------------------------------------------------------
void flushPluginSuite()
{
}

//------------------------------------------------------------------------------
FnPluginStatus setHost(const FnPluginHost *pHost)
{
    const void *pHostSuite = NULL;

    if (pHost == NULL)
    {
        return FnPluginStatusError;
    }

    pHostSuite = pHost->getSuite(MRI_GEO_READER_API_NAME,
        MRI_GEO_READER_API_VERSION);
    if (pHostSuite == NULL)
    {
        return FnPluginStatusError;
    }

    s_Host = *(MriGeoReaderHostV1 *)pHostSuite;
    s_Host.trace("Plug-in connected to host '%s' version '%s' (%u)",
        pHost->name, pHost->versionStr, pHost->versionInt);
    return FnPluginStatusOK;
}

//------------------------------------------------------------------------------

DLLEXPORT FnPlugin *getPlugins(unsigned int *pNumPlugins)
{
    static FnPlugin s_Plugin;

    s_Plugin.name = "AlembicIN geometry generator";
    s_Plugin.pluginVersionMajor = 1;
    s_Plugin.pluginVersionMinor = 0;
    s_Plugin.apiName = MRI_GEO_READER_API_NAME;
    s_Plugin.apiVersion = MRI_GEO_READER_API_VERSION;
    s_Plugin.setHost = &setHost;
    s_Plugin.getSuite = &getPluginSuite;
    s_Plugin.flush = &flushPluginSuite;

    *pNumPlugins = 1;
    return &s_Plugin;
}

//------------------------------------------------------------------------------


